uqmid: sim_fsm: implement PIN/PUK checking set by configuration
authorAlexander Couzens <[email protected]>
Thu, 31 Oct 2024 13:17:03 +0000 (14:17 +0100)
committerDavid Bauer <[email protected]>
Sat, 31 May 2025 20:41:00 +0000 (22:41 +0200)
Signed-off-by: Alexander Couzens <[email protected]>
uqmid/modem.h
uqmid/modem_tx.c
uqmid/modem_tx.h
uqmid/sim_fsm.c
uqmid/sim_fsm.h
uqmid/ubus.c

index 12c0b41c87703e8c3ef00cd00d3a10a4683504aa..0cb2c7621cb0a17dff627a1c2f21193d45e0ea29 100644 (file)
@@ -35,6 +35,7 @@ struct modem_config {
        char *username;
        char *password;
        char *pin;
+       char *puk;
        bool roaming;
        uint8_t pdp_type;
 };
@@ -123,6 +124,9 @@ struct modem {
                bool use_upin;
                bool use_uim;
                bool requires_unlock;
+               /* when we tried the configured pin once */
+               bool pin_tried;
+               bool puk_tried;
                int pin_retries;
                int puk_retries;
        } sim;
index 9ebd4a178d8de6927845b65d22cdc4497c79d7c3..0438dc61e8be6e6db7049cae38801cf9393fb3b1 100644 (file)
@@ -271,3 +271,63 @@ int tx_uim_read_transparent_file(struct modem *modem, struct qmi_service *uim, r
        req->cb_data = modem;
        return uqmi_service_send_msg(uim, req);
 }
+
+int tx_uim_unblock_pin(struct modem *modem, struct qmi_service *uim, request_cb cb,
+                                          QmiUimPinId pin_id, char *new_pin_value, char *puk_value)
+{
+       struct qmi_request *req = talloc_zero(uim, struct qmi_request);
+       struct qmi_msg *msg = talloc_zero_size(req, 1024);
+
+       struct qmi_uim_unblock_pin_request puk_request = {};
+       puk_request.set.info = true;
+       puk_request.data.info.pin_id = pin_id;
+       puk_request.data.info.new_pin = new_pin_value;
+       puk_request.data.info.puk = puk_value;
+
+       /* FIXME: test if this is ok and not QMI_UIM_SESSION_TYPE_CARD_SLOT_1 */
+       puk_request.data.session.session_type = 0;
+       puk_request.data.session.application_identifier_n = 0;
+       puk_request.data.session.application_identifier = NULL;
+
+       int ret = qmi_set_uim_unblock_pin_request(msg, &puk_request);
+       if (ret) {
+               LOG_ERROR("Failed to encode unblock_pin_request");
+               return 1;
+       }
+
+       req->msg = msg;
+       req->cb = cb;
+       req->cb_data = modem;
+       return uqmi_service_send_msg(uim, req);
+}
+
+
+int tx_uim_verify_pin(struct modem *modem, struct qmi_service *uim, request_cb cb,
+                     QmiUimPinId pin_id, char *pin_value)
+{
+       struct qmi_request *req = talloc_zero(uim, struct qmi_request);
+       struct qmi_msg *msg = talloc_zero_size(req, 1024);
+
+       struct qmi_uim_verify_pin_request pin_request = {};
+       pin_request.set.info = true;
+       pin_request.data.info.pin_id = pin_id;
+       pin_request.data.info.pin_value = pin_value;
+
+
+       pin_request.set.session = 1;
+       /* FIXME: test if this is ok and not QMI_UIM_SESSION_TYPE_CARD_SLOT_1 */
+       pin_request.data.session.session_type = 0;
+       pin_request.data.session.application_identifier_n = 0;
+       pin_request.data.session.application_identifier = NULL;
+
+       int ret = qmi_set_uim_verify_pin_request(msg, &pin_request);
+       if (ret) {
+               LOG_ERROR("Failed to encode verify_pin_request");
+               return 1;
+       }
+
+       req->msg = msg;
+       req->cb = cb;
+       req->cb_data = modem;
+       return uqmi_service_send_msg(uim, req);
+}
index 872ebe635e4d8a0c3b74dccd6efbd3d40bd1d689..aa9f3f62ee37e3d593de7a760c0c802be77f219b 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef __UQMID_MODEM_TX_H
 #define __UQMID_MODEM_TX_H
 
+#include "qmi-enums-uim.h"
 #include "uqmid.h"
 
 #include <stdint.h>
@@ -10,8 +11,15 @@ struct qmi_service;
 
 int tx_dms_set_operating_mode(struct modem *modem, struct qmi_service *dms, uint8_t operating_mode, request_cb cb);
 int tx_nas_subscribe_nas_events(struct modem *modem, struct qmi_service *nas, bool action, request_cb cb);
+
 int tx_uim_read_transparent_file(struct modem *modem, struct qmi_service *wds, request_cb cb,
                                  uint16_t file_id, uint8_t *filepath, unsigned int filepath_n);
+int tx_uim_verify_pin(struct modem *modem, struct qmi_service *uim, request_cb cb,
+                                         QmiUimPinId pin_id, char *pin_value);
+
+int tx_uim_unblock_pin(struct modem *modem, struct qmi_service *uim, request_cb cb,
+                                          QmiUimPinId pin_id, char *new_pin_value, char *puk_value);
+
 int tx_wda_set_data_format(struct modem *modem, struct qmi_service *wda, request_cb cb);
 int tx_wds_get_profile_list(struct modem *modem, struct qmi_service *wds, request_cb cb);
 int tx_wds_modify_profile(struct modem *modem, struct qmi_service *wds, request_cb cb, uint8_t profile, const char *apn,
index 6bad1cac8b8f3b8bf822c2303747362dda0097b9..12d2334a48057e84ab18cb91a8818985dd7638bf 100644 (file)
  *
  * tracks the state of the sim and notifies the parent on hard events.
  * e.g. removal of the sim
+ *
+ * The general concept is:
+ * Get information about (IMSI should not be available _before_ PIN/PUK valildation if enabled:
+ *  - ICCID
+ *  - PIN/PUK state
+ *  - SPN, PNN (provider name)
+ *
+ * Unlock if locked by PIN if enough tries are available (more than 1 try).
+ * Enter a failed state if not enough PIN tries are available
+ * Enter a failed state if locked by PUK
+ * If unlocked, read IMSI.
+ *
+ * Further add user interface to:
+ * - unlock PIN/PUK via ubus
+ * - read/write transparent files
+ * - rx/tx APDU (e.g. for eSIM interface)
+ *
+ * See 3GPP TS 31.102 for USIM
  */
 
 #include "logging.h"
@@ -157,6 +175,7 @@ static void uim_get_card_status_cb(struct qmi_service *service, struct qmi_reque
 {
        struct modem *modem = req->cb_data;
        int ret = 0, i = 0, found = 0;
+       bool found_card_state = false;
 
        if (req->ret) {
                modem_log(modem, LOGL_INFO, "Failed to get card status %d/%s.", req->ret, qmi_get_error_str(req->ret));
@@ -173,6 +192,7 @@ static void uim_get_card_status_cb(struct qmi_service *service, struct qmi_reque
        }
 
        for (i = 0; i < res.data.card_status.cards_n; ++i) {
+               found_card_state = true;
                if (res.data.card_status.cards[i].card_state != QMI_UIM_CARD_STATE_PRESENT)
                        continue;
 
@@ -207,8 +227,12 @@ static void uim_get_card_status_cb(struct qmi_service *service, struct qmi_reque
                }
        }
 
-       modem_log(modem, LOGL_INFO, "Failed to find a valid UIM in get_status. Failing UIM.");
-       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_FAILED, NULL);
+       if (found_card_state) {
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_NO_UIM_FOUND, (void *) 1);
+       } else {
+               modem_log(modem, LOGL_INFO, "Failed to find a valid UIM in get_status. Failing UIM.");
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_FAILED, NULL);
+       }
 }
 
 static void uim_read_imsi_cb(struct qmi_service *service, struct qmi_request *req, struct qmi_msg *msg)
@@ -268,26 +292,37 @@ static void sim_st_get_info_on_enter(struct osmo_fsm_inst *fi, uint32_t old_stat
        }
 }
 
-static void sim_st_get_info(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void sim_st_get_info(struct osmo_fsm_inst *fi, uint32_t event, void *_data)
 {
        struct modem *modem = fi->priv;
+       long data = (long) _data;
 
        struct qmi_service *uim = uqmi_service_find(modem->qmi, QMI_SERVICE_UIM);
 
        switch (event) {
        case SIM_EV_RX_UIM_GET_SLOT_FAILED:
        case SIM_EV_RX_UIM_VALID_ICCID:
-       case SIM_EV_RX_UIM_NO_UIM_FOUND:
                /* Get Slot Status succeeded? */
                if (uim && modem->sim.use_uim)
                        uqmi_service_send_simple(uim, qmi_set_uim_get_card_status_request, uim_get_card_status_cb,
                                                 modem);
                break;
-       case SIM_EV_RX_UIM_CARD_STATUS_VALID:
+       case SIM_EV_RX_UIM_NO_UIM_FOUND:
                if (uim && modem->sim.use_uim) {
-                       _tx_uim_read_transparent_file(modem, uim, uim_read_imsi_cb, UIM_FILE_IMSI);
+                       /* Get Slot Status failed, try Get Card Status */
+                       if (data == 0)
+                               uqmi_service_send_simple(uim, qmi_set_uim_get_card_status_request, uim_get_card_status_cb,
+                                                        modem);
+                       /* Get Card Status also failed to get one */
+                       else if (data == 1)
+                               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_NO_SIM_PRESENT, 0, 0);
                }
                break;
+       case SIM_EV_RX_UIM_CARD_STATUS_VALID:
+               /* We found a valid SIM card via card status */
+               if (uim && modem->sim.use_uim)
+                       _tx_uim_read_transparent_file(modem, uim, uim_read_imsi_cb, UIM_FILE_IMSI);
+               break;
        case SIM_EV_RX_UIM_GET_IMSI_FAILED:
        case SIM_EV_RX_UIM_GET_IMSI_SUCCESS:
                switch (modem->sim.state) {
@@ -301,10 +336,12 @@ static void sim_st_get_info(struct osmo_fsm_inst *fi, uint32_t event, void *data
                        osmo_fsm_inst_state_chg(fi, SIM_ST_READY, 0, 0);
                        break;
                case UQMI_SIM_UNKNOWN:
-                       /* FIXME: what to do when unknown? */
+                       modem_log(modem, LOGL_ERROR, "Got unknown SIM state by UIM. Is a SIM present?");
+                       osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_NO_SIM_PRESENT, 0, 0);
                        break;
                case UQMI_SIM_BLOCKED:
-                       osmo_fsm_inst_state_chg(fi, SIM_ST_FAILED, SIM_DEFAULT_TIMEOUT_S, 0);
+                       modem_log(modem, LOGL_ERROR, "SIM is blocked. Can't do anythings");
+                       osmo_fsm_inst_state_chg(fi, SIM_ST_FAILED, 0, 0);
                        break;
                default:
                        /* FIXME: default case */
@@ -314,22 +351,163 @@ static void sim_st_get_info(struct osmo_fsm_inst *fi, uint32_t event, void *data
        }
 }
 
+static void uim_verify_pin_cb(struct qmi_service *service, struct qmi_request *req, struct qmi_msg *msg)
+{
+       struct modem *modem = req->cb_data;
+       int ret = 0;
+
+       struct qmi_uim_verify_pin_response res = {};
+       ret = qmi_parse_uim_verify_pin_response(msg, &res);
+
+       if (req->ret) {
+               modem_log(modem, LOGL_INFO, "Failed to verify PIN. Qmi Ret %d/%s.", req->ret,
+                         qmi_get_error_str(req->ret));
+               if (!ret && res.set.retries_remaining) {
+                       modem->sim.pin_retries = res.data.retries_remaining.verify_retries_left;
+                       modem->sim.puk_retries = res.data.retries_remaining.unblock_retries_left;
+                       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PIN_INVALID, (void *) 1);
+               } else
+                       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PIN_INVALID, (void *) 0);
+               return;
+       }
+
+       if (ret) {
+               modem_log(modem, LOGL_INFO, "Failed to verify pin. Decoder failed. %d", ret);
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_REFRESH, NULL);
+               return;
+       }
+
+       if (res.set.retries_remaining) {
+               modem->sim.pin_retries = res.data.retries_remaining.verify_retries_left;
+               modem->sim.puk_retries = res.data.retries_remaining.unblock_retries_left;
+       }
+
+       if (res.set.card_result && res.data.card_result.sw1 == 0x63) {
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PIN_VERIFIED, NULL);
+               return;
+       }
+
+       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_REFRESH, NULL);
+}
+
+static void pin_verify(struct modem *modem, char *pin)
+{
+       QmiUimPinId pin_id;
+       struct qmi_service *uim = uqmi_service_find(modem->qmi, QMI_SERVICE_UIM);
+
+       modem->sim.pin_tried = true;
+
+       if (modem->sim.use_upin)
+               pin_id = QMI_UIM_PIN_ID_UPIN;
+       else
+               pin_id = QMI_UIM_PIN_ID_PIN1;
+
+       tx_uim_verify_pin(modem, uim, &uim_verify_pin_cb, pin_id, pin);
+}
+
 static void sim_st_chv_pin_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
 {
-       // FIXME: try to unlock if enough tries are there
+       struct modem *modem = fi->priv;
+
+       if (modem->sim.pin_retries < 2 || modem->sim.pin_tried || !modem->config.pin) {
+               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_PIN_REQUIRED, SIM_DEFAULT_TIMEOUT_S, 0);
+               return;
+       }
+
+       pin_verify(modem, modem->config.pin);
 }
 
 static void sim_st_chv_pin(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
+       switch (event) {
+       case SIM_EV_RX_UIM_REFRESH:
+       case SIM_EV_RX_UIM_PIN_VERIFIED:
+               osmo_fsm_inst_state_chg(fi, SIM_ST_GET_INFO, SIM_DEFAULT_TIMEOUT_S, 0);
+               break;
+       case SIM_EV_RX_UIM_PIN_INVALID:
+               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_PIN_REQUIRED, SIM_DEFAULT_TIMEOUT_S, 0);
+               break;
+       }
+}
+
+static void uim_unblock_pin_cb(struct qmi_service *service, struct qmi_request *req, struct qmi_msg *msg)
+{
+       struct modem *modem = req->cb_data;
+       int ret = 0;
+
+       struct qmi_uim_unblock_pin_response res = {};
+       ret = qmi_parse_uim_unblock_pin_response(msg, &res);
+
+       if (req->ret) {
+               modem_log(modem, LOGL_INFO, "Failed to unblock PIN by PUK. Qmi Ret %d/%s.", req->ret,
+                         qmi_get_error_str(req->ret));
+               if (!ret && res.set.retries_remaining) {
+                       modem->sim.pin_retries = res.data.retries_remaining.verify_retries_left;
+                       modem->sim.puk_retries = res.data.retries_remaining.unblock_retries_left;
+                       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PUK_INVALID, (void *) 1);
+               } else
+                       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PUK_INVALID, (void *) 0);
+               return;
+       }
+
+       if (ret) {
+               modem_log(modem, LOGL_INFO, "Failed to unblock pin/puk. Decoder failed. %d", ret);
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_REFRESH, NULL);
+               return;
+       }
+
+       if (res.set.retries_remaining) {
+               modem->sim.pin_retries = res.data.retries_remaining.verify_retries_left;
+               modem->sim.puk_retries = res.data.retries_remaining.unblock_retries_left;
+       }
+
+       if (res.set.card_result && res.data.card_result.sw1 == 0x63) {
+               osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_PUK_VERIFIED, NULL);
+               return;
+       }
+
+       osmo_fsm_inst_dispatch(modem->sim.fi, SIM_EV_RX_UIM_REFRESH, NULL);
+}
+
+static int pin_unblock(struct modem *modem, char *new_pin, char *puk)
+{
+       QmiUimPinId pin_id;
+       struct qmi_service *uim = uqmi_service_find(modem->qmi, QMI_SERVICE_UIM);
+
+       modem->sim.puk_tried = true;
+       if (modem->sim.use_upin)
+               pin_id = QMI_UIM_PIN_ID_UPIN;
+       else
+               pin_id = QMI_UIM_PIN_ID_PIN1;
+
+       return tx_uim_unblock_pin(modem, uim, &uim_unblock_pin_cb, pin_id, new_pin, puk);
 }
 
 static void sim_st_chv_puk_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
 {
-       // FIXME: try to unlock if enough tries are there
+       struct modem *modem = fi->priv;
+
+       if (modem->sim.puk_retries < 2 || modem->sim.puk_tried || !modem->config.puk || !modem->config.pin) {
+               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_PUK_REQUIRED, SIM_DEFAULT_TIMEOUT_S, 0);
+               return;
+       }
+
+
+       if (pin_unblock(modem, modem->config.pin, modem->config.puk))
+               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_PUK_REQUIRED, 0, 0);
 }
 
 static void sim_st_chv_puk(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
+       switch (event) {
+       case SIM_EV_RX_UIM_REFRESH:
+       case SIM_EV_RX_UIM_PIN_VERIFIED:
+               osmo_fsm_inst_state_chg(fi, SIM_ST_GET_INFO, SIM_DEFAULT_TIMEOUT_S, 0);
+               break;
+       case SIM_EV_RX_UIM_PIN_INVALID:
+               osmo_fsm_inst_state_chg(fi, SIM_ST_FAIL_PIN_REQUIRED, SIM_DEFAULT_TIMEOUT_S, 0);
+               break;
+       }
 }
 
 static void sim_st_ready_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
@@ -343,6 +521,18 @@ static void sim_st_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 }
 
+static void sim_st_fail_pin_required(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void sim_st_fail_puk_required(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void sim_st_fail_no_sim_present(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
 static void sim_st_failed(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 }
@@ -433,6 +623,7 @@ static const struct osmo_fsm_state sim_states[] = {
                .out_state_mask = S(SIM_ST_READY) |
                                  S(SIM_ST_CHV_PIN) |
                                  S(SIM_ST_CHV_PUK) |
+                                 S(SIM_ST_FAIL_NO_SIM_PRESENT) |
                                  S(SIM_ST_DESTROY),
                .name = "GET_INFO",
                .onenter = sim_st_get_info_on_enter,
@@ -459,6 +650,24 @@ static const struct osmo_fsm_state sim_states[] = {
                .onenter = sim_st_chv_puk_on_enter,
                .action = sim_st_chv_puk,
        },
+       [SIM_ST_FAIL_PIN_REQUIRED] = {
+               .in_event_mask = 0,
+               .out_state_mask = S(SIM_ST_DESTROY) | S(SIM_ST_GET_INFO),
+               .name = "FAIL_PIN_REQUIRED",
+               .action = sim_st_fail_pin_required,
+       },
+       [SIM_ST_FAIL_PUK_REQUIRED] = {
+               .in_event_mask = 0,
+               .out_state_mask = S(SIM_ST_DESTROY) | S(SIM_ST_GET_INFO),
+               .name = "FAIL_PUK_REQUIRED",
+               .action = sim_st_fail_puk_required,
+       },
+       [SIM_ST_FAIL_NO_SIM_PRESENT] = {
+               .in_event_mask = 0,
+               .out_state_mask = S(SIM_ST_DESTROY) | S(SIM_ST_GET_INFO),
+               .name = "NO_SIM_PRESENT",
+               .action = sim_st_fail_no_sim_present,
+       },
        [SIM_ST_FAILED] = {
                .in_event_mask = 0,
                .out_state_mask = S(SIM_ST_DESTROY) | S(SIM_ST_GET_INFO),
index 4577cab2f4ff8886fdb617c7dc7d9714f5d3a5d7..94ef38e01bc17de17e943c66643dbf356fab5f6f 100644 (file)
@@ -2,6 +2,7 @@
 #define __UQMID_SIM_FSM_H
 
 #include <stdbool.h>
+
 enum sim_fsm_state {
        SIM_ST_IDLE,
        SIM_ST_WAIT_UIM_PRESENT,
@@ -10,7 +11,10 @@ enum sim_fsm_state {
        SIM_ST_CHV_PUK,
        SIM_ST_READY,
        SIM_ST_FAILED,
-       SIM_ST_REMOVED,
+       SIM_ST_NO_PIN_TRIES_LEFT,
+       SIM_ST_FAIL_PUK_REQUIRED,
+       SIM_ST_FAIL_PIN_REQUIRED,
+       SIM_ST_FAIL_NO_SIM_PRESENT,
        SIM_ST_DESTROY,
 };
 
@@ -38,7 +42,14 @@ enum sim_fsm_event {
        SIM_EV_RX_UIM_GET_IMSI_FAILED,
 
        SIM_EV_RX_UIM_PIN_REQUIRED,
+       SIM_EV_RX_UIM_PIN_INVALID,
+       SIM_EV_RX_UIM_PIN_VERIFIED,
+
        SIM_EV_RX_UIM_PUK_REQUIRED,
+       SIM_EV_RX_UIM_PUK_INVALID,
+       SIM_EV_RX_UIM_PUK_VERIFIED,
+
+       SIM_EV_RX_UIM_REFRESH, /* we need to re-check the UIM. E.g. this can happen when pin verified or puk applied, but with a result which we can't parse */
        SIM_EV_RX_UIM_READY,
 
        SIM_EV_RX_FAILED,
index 6e6ad5fcb6772627b7a48c0fd31d03d9105e3aff..077b4afa1ddb438121e5582d486005af1c28bb78 100644 (file)
@@ -272,12 +272,13 @@ static int modem_remove(struct ubus_context *ctx, struct ubus_object *obj, struc
        return UBUS_STATUS_OK;
 }
 
-enum { CFG_APN, CFG_PIN, CFG_ROAMING, CFG_USERNAME, CFG_PASSWORD, __CFG_MAX };
+enum { CFG_APN, CFG_PIN, CFG_PUK, CFG_ROAMING, CFG_USERNAME, CFG_PASSWORD, __CFG_MAX };
 
 /** ubus call modem_configure{apn: internet, pin: 2342, roaming: false}` */
 static const struct blobmsg_policy modem_configure_policy[__CFG_MAX] = {
        [CFG_APN] = { .name = "apn", .type = BLOBMSG_TYPE_STRING },
        [CFG_PIN] = { .name = "pin", .type = BLOBMSG_TYPE_STRING },
+       [CFG_PUK] = { .name = "puk", .type = BLOBMSG_TYPE_STRING },
        [CFG_ROAMING] = { .name = "roaming", .type = BLOBMSG_TYPE_BOOL },
        [CFG_USERNAME] = { .name = "username", .type = BLOBMSG_TYPE_STRING },
        [CFG_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING },
@@ -308,6 +309,13 @@ static int modem_configure(struct ubus_context *ctx, struct ubus_object *obj, st
                        modem->config.pin = talloc_strdup(modem, blobmsg_get_string(tb[CFG_PIN]));
        }
 
+       if (tb[CFG_PUK]) {
+               TALLOC_FREE(modem->config.puk);
+               value = blobmsg_get_string(tb[CFG_PUK]);
+               if (value && strlen(value))
+                       modem->config.puk = talloc_strdup(modem, blobmsg_get_string(tb[CFG_PUK]));
+       }
+
        if (tb[CFG_ROAMING]) {
                modem->config.roaming = blobmsg_get_bool(tb[CFG_ROAMING]);
        }